Skip to content

feat: replace snapshotId with name-based sandbox persistence#114

Open
recoup-coding-agent wants to merge 1 commit intomainfrom
feature/REC-36-sandbox-names
Open

feat: replace snapshotId with name-based sandbox persistence#114
recoup-coding-agent wants to merge 1 commit intomainfrom
feature/REC-36-sandbox-names

Conversation

@recoup-coding-agent
Copy link
Copy Markdown
Collaborator

@recoup-coding-agent recoup-coding-agent commented Mar 29, 2026

Summary

  • Replaces explicit sandbox.snapshot() calls with Vercel's name-based sandbox persistence
  • Removes snapshotId from task schemas, callback payloads, and return types
  • Uses Sandbox.get({ name }) instead of Sandbox.get({ sandboxId }) throughout
  • snapshotAndPersist now only persists github_repo metadata (no more snapshot-taking)
  • Updates all related tests to match new behavior

Part of REC-36. Companion PR in api repo handles the API-side changes.

Test plan

  • All 182 tasks tests pass
  • Verify sandbox creation uses name-based persistence in staging
  • Verify coding agent task completes without snapshot errors
  • Verify update-pr task resumes sandbox by name correctly

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • Refactor
    • Removed snapshot expiration and ID fields from sandbox persistence. Sandboxes are now identified by name instead of snapshot ID.
    • Simplified payload structures by eliminating snapshotId from callbacks and API responses.
    • Updated sandbox metadata persistence to focus on account-level data and GitHub repository associations rather than snapshot management.

With Vercel's new sandbox names feature, sandbox state persists
automatically by name. This removes explicit snapshot-taking and
snapshotId passing throughout the tasks codebase.

- Remove snapshot() calls from codingAgentTask, updatePRTask, setupSandboxTask
- Remove snapshotId from schemas, callbacks, and return types
- Use Sandbox.get({ name }) instead of Sandbox.get({ sandboxId })
- snapshotAndPersist now only persists github_repo metadata
- Update all related tests

Co-Authored-By: Paperclip <noreply@paperclip.ing>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 29, 2026

📝 Walkthrough

Walkthrough

This PR deprecates snapshot management across the codebase by removing snapshotId parameters and schema fields, replacing snapshot-taking logic with simplified account-level metadata persistence via updateAccountSnapshot, and switching sandbox identification from sandboxId to name for Vercel API calls.

Changes

Cohort / File(s) Summary
Account Snapshot Persistence
src/recoup/updateAccountSnapshot.ts
Deprecated the snapshotId parameter (renamed to _snapshotId), removed it from request payloads, and simplified return type from { success: boolean; snapshotId: string } to { success: boolean }.
Sandbox Provisioning
src/sandboxes/getOrCreateSandbox.ts, src/sandboxes/ensureGithubRepo.ts
Changed Sandbox.get() to use name parameter instead of sandboxId, and updated persistence calls to pass undefined for the deprecated snapshot ID while maintaining githubRepo argument.
Snapshot & Metadata Persistence
src/sandboxes/snapshotAndPersist.ts, src/sandboxes/notifyCodingAgentCallback.ts
Removed sandbox.snapshot() calls, replaced snapshot result returns with simple updateAccountSnapshot() calls, and removed snapshotId field from callback payload type.
Schema Updates
src/schemas/codingAgentSchema.ts, src/schemas/sandboxSchema.ts, src/schemas/updatePRSchema.ts
Removed snapshotId/snapshotSchema fields and snapshot property from exported type definitions, eliminating snapshot validation and references across schemas.
Task Implementations
src/tasks/codingAgentTask.ts, src/tasks/setupSandboxTask.ts, src/tasks/updatePRTask.ts, src/tasks/runSandboxCommandTask.ts
Removed snapshot-taking steps, updated sandbox retrieval to use Sandbox.get({ name: ... }) instead of create(), removed snapshotId from returned results and callback payloads, and updated logging to reference sandbox.name.
Test Updates
src/tasks/__tests__/*, src/sandboxes/__tests__/*
Updated mocks to remove snapshot-related behavior, changed sandbox mock fields from sandboxId to name, removed snapshotId assertions, and updated expectations for persistence calls with undefined snapshot ID.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 Snapshots fade like morning dew,
As metadata takes their place anew.
By name, not ID, sandboxes now call,
Simpler persistence through it all! 🌿

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and accurately summarizes the main objective: replacing snapshotId with name-based sandbox persistence across the codebase.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/REC-36-sandbox-names

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/sandboxes/getOrCreateSandbox.ts (1)

30-35: ⚠️ Potential issue | 🔴 Critical

Sandbox.get() requires sandboxId parameter, not name.

The official Vercel @vercel/sandbox SDK documentation confirms the signature is Sandbox.get({ sandboxId: string; ... }). The current code passes name: created.sandboxId which will fail at runtime. The tests pass because they use mocked implementations that don't validate parameter names, but the real SDK will reject this parameter.

This same issue affects src/tasks/updatePRTask.ts and src/tasks/runSandboxCommandTask.ts.

Suggested fix
   const sandbox = await Sandbox.get({
-    name: created.sandboxId,
+    sandboxId: created.sandboxId,
     token,
     teamId,
     projectId,
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sandboxes/getOrCreateSandbox.ts` around lines 30 - 35, The call to
Sandbox.get uses the wrong parameter key; change the object passed to
Sandbox.get in getOrCreateSandbox (replace name: created.sandboxId with
sandboxId: created.sandboxId) and make the same fix in the other affected call
sites referenced in the review (updatePRTask.ts and runSandboxCommandTask.ts),
ensuring every invocation uses Sandbox.get({ sandboxId: <id>, token, teamId,
projectId }) so it matches the SDK signature.
🧹 Nitpick comments (2)
src/tasks/setupSandboxTask.ts (1)

8-12: Stale JSDoc comment references snapshot behavior.

The comment still mentions "takes a snapshot" but the task no longer takes snapshots—it only persists metadata.

📝 Proposed fix
 /**
  * Background task that creates a personal Vercel Sandbox for an account,
- * fully provisions it (OpenClaw, GitHub repo, org repos, folder structure),
- * takes a snapshot, and shuts it down.
+ * fully provisions it (OpenClaw, GitHub repo, org repos, folder structure),
+ * persists metadata, and shuts it down.
  */
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tasks/setupSandboxTask.ts` around lines 8 - 12, Update the stale JSDoc in
setupSandboxTask.ts: remove the "takes a snapshot" wording and change the
description to state that the task persists sandbox metadata rather than
capturing a snapshot. Locate the top-of-file comment for the background task
(setupSandboxTask) and replace the snapshot phrase with a brief note that it
"persists sandbox metadata and shuts it down" so the doc matches the current
behavior.
src/tasks/updatePRTask.ts (1)

24-37: Consider using getOrCreateSandbox for consistency.

Other tasks like codingAgentTask and setupSandboxTask use getOrCreateSandbox which encapsulates sandbox credential retrieval and provides a consistent pattern. This task bypasses that abstraction, directly calling Sandbox.get.

If the sandbox name concern above is resolved, consider whether this inconsistency is intentional or if using getOrCreateSandbox (or a similar reusable pattern) would be better.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tasks/updatePRTask.ts` around lines 24 - 37, The code in run
(updatePRTask) directly calls Sandbox.get with manual credentials (using
getVercelSandboxCredentials and CODING_AGENT_ACCOUNT_ID) instead of the shared
helper; replace that block to call getOrCreateSandbox (the same helper used by
codingAgentTask and setupSandboxTask), remove the manual
getVercelSandboxCredentials usage, and use the sandbox returned by
getOrCreateSandbox for logging and later use; update references from
Sandbox.get(...) to getOrCreateSandbox(...) so credential handling and sandbox
creation/resumption are consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/recoup/updateAccountSnapshot.ts`:
- Around line 12-16: The updateAccountSnapshot function currently drops the
optional _snapshotId and so no longer clears persisted snapshot_id in the
backend; modify updateAccountSnapshot (and any call sites that pass _snapshotId)
to explicitly send a clear value (e.g., null or empty-string per API contract)
when the intent is to remove the saved snapshot_id so the backend restores do
not keep stale state; ensure the request body includes snapshot_id: null (or "")
rather than omitting the field, and coordinate with
src/recoup/getAccountSandboxes (which still models snapshot_id) and
src/sandboxes/getOrCreateSandbox (which expects POST /api/sandboxes to restore)
to preserve the intended migration semantics.

In `@src/tasks/updatePRTask.ts`:
- Around line 30-35: The code in updatePRTask uses CODING_AGENT_ACCOUNT_ID as
the sandbox name when calling Sandbox.get, but createAccountSandbox returns an
API-generated sandboxId; update updatePRTask to use the actual sandbox ID
returned/stored by getOrCreateSandbox/createAccountSandbox instead of
CODING_AGENT_ACCOUNT_ID — locate the resume logic in updatePRTask where
Sandbox.get({... name: CODING_AGENT_ACCOUNT_ID ...}) is called and replace it
with the stored sandboxId (or call getOrCreateSandbox and pass its
created.sandboxId) so Sandbox.get receives the API-generated sandbox name;
ensure any initialization that persists the sandbox ID (from
createAccountSandbox or getOrCreateSandbox) is read before calling Sandbox.get.

---

Outside diff comments:
In `@src/sandboxes/getOrCreateSandbox.ts`:
- Around line 30-35: The call to Sandbox.get uses the wrong parameter key;
change the object passed to Sandbox.get in getOrCreateSandbox (replace name:
created.sandboxId with sandboxId: created.sandboxId) and make the same fix in
the other affected call sites referenced in the review (updatePRTask.ts and
runSandboxCommandTask.ts), ensuring every invocation uses Sandbox.get({
sandboxId: <id>, token, teamId, projectId }) so it matches the SDK signature.

---

Nitpick comments:
In `@src/tasks/setupSandboxTask.ts`:
- Around line 8-12: Update the stale JSDoc in setupSandboxTask.ts: remove the
"takes a snapshot" wording and change the description to state that the task
persists sandbox metadata rather than capturing a snapshot. Locate the
top-of-file comment for the background task (setupSandboxTask) and replace the
snapshot phrase with a brief note that it "persists sandbox metadata and shuts
it down" so the doc matches the current behavior.

In `@src/tasks/updatePRTask.ts`:
- Around line 24-37: The code in run (updatePRTask) directly calls Sandbox.get
with manual credentials (using getVercelSandboxCredentials and
CODING_AGENT_ACCOUNT_ID) instead of the shared helper; replace that block to
call getOrCreateSandbox (the same helper used by codingAgentTask and
setupSandboxTask), remove the manual getVercelSandboxCredentials usage, and use
the sandbox returned by getOrCreateSandbox for logging and later use; update
references from Sandbox.get(...) to getOrCreateSandbox(...) so credential
handling and sandbox creation/resumption are consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f8904621-6cec-43b1-bd16-34c52ec09ed5

📥 Commits

Reviewing files that changed from the base of the PR and between 01ba744 and 0c669b3.

📒 Files selected for processing (18)
  • src/recoup/updateAccountSnapshot.ts
  • src/sandboxes/__tests__/getOrCreateSandbox.test.ts
  • src/sandboxes/__tests__/notifyCodingAgentCallback.test.ts
  • src/sandboxes/__tests__/snapshotAndPersist.test.ts
  • src/sandboxes/ensureGithubRepo.ts
  • src/sandboxes/getOrCreateSandbox.ts
  • src/sandboxes/notifyCodingAgentCallback.ts
  • src/sandboxes/snapshotAndPersist.ts
  • src/schemas/codingAgentSchema.ts
  • src/schemas/sandboxSchema.ts
  • src/schemas/updatePRSchema.ts
  • src/tasks/__tests__/codingAgentTask.test.ts
  • src/tasks/__tests__/setupSandboxTask.test.ts
  • src/tasks/__tests__/updatePRTask.test.ts
  • src/tasks/codingAgentTask.ts
  • src/tasks/runSandboxCommandTask.ts
  • src/tasks/setupSandboxTask.ts
  • src/tasks/updatePRTask.ts
💤 Files with no reviewable changes (5)
  • src/sandboxes/tests/notifyCodingAgentCallback.test.ts
  • src/schemas/codingAgentSchema.ts
  • src/schemas/updatePRSchema.ts
  • src/sandboxes/notifyCodingAgentCallback.ts
  • src/schemas/sandboxSchema.ts

Comment on lines 12 to +16
export async function updateAccountSnapshot(
accountId: string,
snapshotId?: string,
_snapshotId?: string,
githubRepo?: string
): Promise<{ success: boolean; snapshotId: string } | undefined> {
): Promise<{ success: boolean } | undefined> {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

This no longer clears persisted snapshot_id state.

src/recoup/getAccountSandboxes.ts:5-9 still models snapshot_id, and src/sandboxes/getOrCreateSandbox.ts:12-13 says POST /api/sandboxes still performs restoration internally. Deprecating _snapshotId without sending an explicit clear value means old snapshot links can survive this rollout, which makes the name-based migration incomplete and can keep the backend restoring stale sandbox state.

Also applies to: 22-25

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/recoup/updateAccountSnapshot.ts` around lines 12 - 16, The
updateAccountSnapshot function currently drops the optional _snapshotId and so
no longer clears persisted snapshot_id in the backend; modify
updateAccountSnapshot (and any call sites that pass _snapshotId) to explicitly
send a clear value (e.g., null or empty-string per API contract) when the intent
is to remove the saved snapshot_id so the backend restores do not keep stale
state; ensure the request body includes snapshot_id: null (or "") rather than
omitting the field, and coordinate with src/recoup/getAccountSandboxes (which
still models snapshot_id) and src/sandboxes/getOrCreateSandbox (which expects
POST /api/sandboxes to restore) to preserve the intended migration semantics.

Comment on lines +30 to 35
const sandbox = await Sandbox.get({
name: CODING_AGENT_ACCOUNT_ID,
token,
teamId,
projectId,
source: { type: "snapshot", snapshotId },
timeout: 30 * 60 * 1000,
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check what createAccountSandbox returns and how sandbox names are determined

# Find createAccountSandbox implementation
echo "=== createAccountSandbox implementation ==="
ast-grep --pattern 'async function createAccountSandbox($_) {
  $$$
}'

# Also check with export syntax
ast-grep --pattern 'export async function createAccountSandbox($_) {
  $$$
}'

# Search for createAccountSandbox to find its definition
echo ""
echo "=== Searching for createAccountSandbox definition ==="
rg -n "createAccountSandbox" -A 20 --type ts

# Check if sandbox name uses accountId directly anywhere
echo ""
echo "=== Check Sandbox.get/create name parameter patterns ==="
rg -n "Sandbox\.(get|create)" -A 5 --type ts

Repository: recoupable/tasks

Length of output: 10841


Fix sandbox name mismatch: use actual sandbox ID instead of account ID.

updatePRTask attempts to resume the sandbox using CODING_AGENT_ACCOUNT_ID directly as the sandbox name. However, createAccountSandbox() returns an API-generated sandboxId (e.g., "sbx_123"), which is passed to Sandbox.get({ name: created.sandboxId }) in getOrCreateSandbox.

The constant CODING_AGENT_ACCOUNT_ID is a UUID that will never match the actual sandbox name created by the API. This will cause Sandbox.get() to fail at runtime, preventing updatePRTask from connecting to the sandbox.

To fix: Pass the sandbox ID returned from createAccountSandbox() to Sandbox.get(), or update the initialization logic to store and retrieve the actual sandbox ID before updatePRTask attempts to resume it.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tasks/updatePRTask.ts` around lines 30 - 35, The code in updatePRTask
uses CODING_AGENT_ACCOUNT_ID as the sandbox name when calling Sandbox.get, but
createAccountSandbox returns an API-generated sandboxId; update updatePRTask to
use the actual sandbox ID returned/stored by
getOrCreateSandbox/createAccountSandbox instead of CODING_AGENT_ACCOUNT_ID —
locate the resume logic in updatePRTask where Sandbox.get({... name:
CODING_AGENT_ACCOUNT_ID ...}) is called and replace it with the stored sandboxId
(or call getOrCreateSandbox and pass its created.sandboxId) so Sandbox.get
receives the API-generated sandbox name; ensure any initialization that persists
the sandbox ID (from createAccountSandbox or getOrCreateSandbox) is read before
calling Sandbox.get.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant